#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <syscall.h>
#include <stdbool.h>
#include <sys/time.h>

#define THREAD_NUM 8
#define NUMBER_N   16
#define QUEUE_LEN  (NUMBER_N + 1)
#define R_TASK_NUM (NUMBER_N - 1)

typedef struct Queue {
    int head, tail, cnt;
    int element[NUMBER_N + 1];
}Queue;

typedef struct Monitor {
    int task_left;
    Queue num_queue;
    pthread_mutex_t lock;
    pthread_cond_t can_reduce;
}Monitor;

Monitor reduce_monitor;

bool queue_empty(Queue * q)
{
    return q->head == q->tail;
}
bool queue_full(Queue * q)
{
    return (q->head + 1) % QUEUE_LEN == q->tail % QUEUE_LEN;
}
void init_queue(Queue * q)
{
    q->cnt = q->head = q->tail = 0;
}

void enqueue(Queue * q, int e)
{
    if(!queue_full(q))
    {
        q->element[q->head] = e;
        q->head ++;
        q->head %= QUEUE_LEN;
        q->cnt ++;
        // printf("head %d, tail %d, cnt %d\n",q->head,q->tail,q->cnt);
    }
    else
    {
        printf("Queue error!\n");
        exit(1);
    }
}
int dequeue(Queue * q)
{
    int temp;
    if(!queue_empty(q))
    {
        temp = q->element[q->tail];
        q->tail ++;
        q->tail %= QUEUE_LEN;
        q->cnt --;
        // printf("head %d, tail %d, cnt %d\n",q->head,q->tail,q->cnt);
        return temp;
    }
    else
    {
        printf("Queue empty error!\n");
        exit(1);
    }
    
}
void print_queue(Queue *q)
{
    printf("Queue: ");
    for(int i = q->tail;i!=q->head;i++,i%=QUEUE_LEN)
    {
        printf("%d ",q->element[i]);
    }
    printf("\n");
}

void monitor_init(Monitor * m)
{
    m->task_left = R_TASK_NUM;
    init_queue(&m->num_queue);
    pthread_mutex_init(&m->lock,NULL);
    pthread_cond_init(&m->can_reduce,NULL);
    for(int i = 0; i<NUMBER_N;i++)
    {
        enqueue(&m->num_queue,rand()%50);
    }
}
bool monitor_gettask(Monitor * m, int * a, int * b)
{
    // printf("Try to get task\n");
    pthread_mutex_lock(&m->lock);
    // printf("Task left: %d\n",m->task_left);
    if(m->task_left <= 0)
    {
        pthread_mutex_unlock(&m->lock);
        return false;
    }
    // the condition is ok, and the mutex is back
    m->task_left -= 1;
    while(m->num_queue.cnt < 2)
        pthread_cond_wait(&m->can_reduce, &m->lock);
    *a = dequeue(&m->num_queue);
    *b = dequeue(&m->num_queue);
    printf("[get] %d %d\n",*a,*b);
    pthread_mutex_unlock(&m->lock);
    return true;
}
bool monitor_putresult(Monitor * m, int r)
{
    pthread_mutex_lock(&m->lock);
    enqueue(&m->num_queue,r);
    printf("[put] %d\n",r);
    pthread_cond_signal(&m->can_reduce);
    // pthread_cond_broadcast(&m->can_reduce);
    pthread_mutex_unlock(&m->lock);
}
void * func_reduce_task(void * arg)
{
    int a,b,res;
    while(1)
    {
        if(!monitor_gettask(&reduce_monitor, &a, &b))
        {
            printf("[Thread Exit]\n");
            return NULL;
        }
        res = a+b;
        usleep(1000 * (1+ (rand() % 10))); // 1~10us
        monitor_putresult(&reduce_monitor,res);
    }
    return NULL;
}

int main()
{
    pthread_t threads[THREAD_NUM];
    monitor_init(&reduce_monitor);
    print_queue(&reduce_monitor.num_queue);
    for(int i = 0; i< THREAD_NUM;i++)
    {
        pthread_create(&threads[i],NULL,func_reduce_task,NULL);
    }
    for(int i = 0; i< THREAD_NUM;i++)
    {
        pthread_join(threads[i],NULL);
    }
    printf("[Done]\n");
    print_queue(&reduce_monitor.num_queue);
}

